package avicit.bdp.dds.api.service;

import avicit.bdp.common.base.BaseService;
import avicit.bdp.common.dto.StorageResourceConf;
import avicit.bdp.common.service.service.CommonTypeService;
import avicit.bdp.common.utils.BdpLogUtil;
import avicit.bdp.common.utils.EntityUtils;
import avicit.bdp.common.utils.uploads.FileAdapterUtils;
import avicit.bdp.common.utils.uploads.IFileAdapter;
import avicit.bdp.core.constant.Constants;
import avicit.bdp.dds.dispatch.enums.ProgramType;
import avicit.bdp.dds.dao.entity.AlgoNodes;
import avicit.bdp.dds.dao.entity.Project;
import avicit.bdp.dds.dao.entity.ProjectAlgoNodes;
import avicit.bdp.dds.dao.entity.Resource;
import avicit.bdp.dds.dao.mapper.AlgoNodesMapper;
import avicit.bdp.dds.dao.mapper.ProjectMapper;
import avicit.bdp.dds.dao.mapper.ResourceMapper;
import avicit.platform6.commons.utils.ComUtil;
import avicit.platform6.core.exception.BusinessException;
import avicit.platform6.core.exception.BusinessException;
import avicit.platform6.core.properties.PlatformConstant;
import avicit.platform6.core.rest.msg.QueryRespBean;
import com.github.pagehelper.Page;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * @author felix project service
 */
@Service
public class AlgoNodesService extends BaseService<AlgoNodesMapper, AlgoNodes> {

  private static final Logger logger = LoggerFactory.getLogger(AlgoNodesService.class);
  @Autowired private ResourceMapper resourceMapper;
  @Autowired private ProjectAlgoNodesService projectAlgoNodesService;
  @Autowired private CommonTypeService commonTypeService;
  @Autowired private ProjectMapper projectMapper;

  /**
   * 新增算法库
   *
   * @param name name
   * @param programType programType
   * @param typeId typeId
   * @param className className
   * @param open open
   * @param remark remark
   * @param file file
   * @param projectId projectId
   */
  @Transactional(rollbackFor = Exception.class)
  public String insertNode(
      String name,
      Integer programType,
      String typeId,
      String className,
      Integer open,
      String remark,
      String content,
      MultipartFile file,
      String projectId,
      String params) {
    try {
      if (StringUtils.isBlank(name)) {
        throw new BusinessException("算法库名称为空");
      }
      // 判断文件大小
      if (file != null && file.getSize() > Constants.GB_IN_BYTES) {
        logger.error("file [{}] size is too large, more than 1GB.", file.getOriginalFilename());
        throw new BusinessException("文件大于1G，不能上传");
      }
      // 组装UDF
      String nodeId = ComUtil.getId();
      AlgoNodes node = new AlgoNodes();
      node.setId(nodeId);
      node.setName(name);
      node.setProgramType(programType);
      node.setClassName(className);
      node.setOpen(open);
      node.setRemark(remark);
      node.setParams(params);
      node.setContent(content);
      if (file != null) {
        // 组装Resource
        Resource resource = new Resource();
        String resourceId = ComUtil.getId();
        resource.setId(resourceId);

        String originalFilename = file.getOriginalFilename();
        // 获取文件后缀
        String fileSuffix =
            originalFilename == null
                ? null
                : originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
        // 获取文件名称
        String fileName = originalFilename;

        // 读取文件中json文件
        if (Constants.ZIP.equals(fileSuffix) || Constants.JAR.equals(fileSuffix)) {
          try {
            ZipInputStream zs = new ZipInputStream(file.getInputStream(), Charset.forName("GBK"));
            BufferedInputStream bs = new BufferedInputStream(zs);
            byte[] bytes;
            ZipEntry ze;
            while ((ze = zs.getNextEntry()) != null) {
              // 获取zip包中的每一个zip file entry
              StringBuilder orginJson = new StringBuilder();
              // jsonName
              String jsonName = ze.getName();
              String jsonPath = "";
              if (ProgramType.JAVA.getCode() == programType) {
                jsonPath = className.replace(".", Constants.SINGLE_SLASH) + ".json";
              } else {
                jsonPath =
                    className
                            .substring(0, className.lastIndexOf("."))
                            .replace(".", Constants.SINGLE_SLASH)
                        + ".json";
              }
              if (jsonPath.equals(jsonName)) {
                long size = ze.getSize();
                if (size == -1) {
                  ByteArrayOutputStream baos = new ByteArrayOutputStream();
                  while (true) {
                    int bsize = zs.read();
                    if (bsize == -1) {
                      break;
                    }
                    baos.write(bsize);
                  }
                  orginJson = orginJson.append(new String(baos.toByteArray()));
                  baos.close();
                } else {
                  //
                  // 读取每个文件的字节，并放进数组
                  bytes = new byte[(int) ze.getSize()];
                  bs.read(bytes, 0, (int) ze.getSize());
                  // 将文件转成流
                  InputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
                  BufferedReader br =
                      new BufferedReader(new InputStreamReader(byteArrayInputStream));
                  // 读取文件里面的内容
                  String line;
                  while ((line = br.readLine()) != null) {
                    orginJson.append(line);
                  }
                  // 关闭流
                  br.close();
                }
                node.setParams(orginJson.toString());
              }
            }
            bs.close();
            zs.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }

        resource.setSize(file.getSize());
        resource.setName(fileName);
        resource.setFileSuffix(fileSuffix);
        // 默认文件
        resource.setDirectory(Resource.DIRECTORY_FILE);
        // 类型为算法库
        resource.setType(Resource.DTYPE_NODE);
        resource.setProjectId(projectId);

        StorageResourceConf conf = FileAdapterUtils.getStorageResourceByProjectId(projectId);
        if (!FileAdapterUtils.TYPE_HDFS.equals(conf.getType())) {
          throw new BusinessException("minio存储引擎目前不支持算法库的增加！算法库目前只支持hdfs存储引擎。");
        }
        IFileAdapter fileAdapter = FileAdapterUtils.getFileAdapterByProject(projectId);

        resource.setEngineResourceId(conf.getId());

        Date d = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String dateNowStr = sdf.format(d);

        // 如果文件同名，则创建一个随机的目录存放
        boolean flag = this.isSameResourceName(projectId, fileName);
        if (flag) {
          dateNowStr += Constants.SINGLE_SLASH + UUID.randomUUID().toString();
        }

        String fullName =
            Constants.ALGONODE_UPLOAD_TOP_PATH
                + Constants.SINGLE_SLASH
                + projectId
                + Constants.SINGLE_SLASH
                + dateNowStr
                + Constants.SINGLE_SLASH
                + originalFilename;

        fileAdapter.upload(file.getInputStream(), fullName);

        resource.setFullName(fullName);
        EntityUtils.setCreateAndUpdateInfo(resource);
        resourceMapper.insertSelective(resource);
        node.setResourceId(resourceId);
      }

      insertSelective(node);

      // 插入关系表
      ProjectAlgoNodes projectAlgoNodes = new ProjectAlgoNodes();
      projectAlgoNodes.setId(ComUtil.getId());
      projectAlgoNodes.setNodesId(nodeId);
      projectAlgoNodes.setTypeId(typeId);
      projectAlgoNodes.setProjectId(projectId);
      projectAlgoNodesService.insertSelective(projectAlgoNodes);
      // 记录日志
      BdpLogUtil.log4Insert(node);
      return nodeId;
    } catch (Exception e) {
      throw new BusinessException(e.getMessage(), e);
    }
  }

  /**
   * 新增算法
   *
   * @param algoNodes
   * @return
   */
  @Transactional(rollbackFor = Exception.class)
  public String insertNode(AlgoNodes algoNodes) {
    if (algoNodes == null) {
      throw new BusinessException("参数为空.");
    }
    if (StringUtils.isBlank(algoNodes.getName())) {
      throw new BusinessException("算法库名称为空");
    }
    if (StringUtils.isBlank(algoNodes.getTypeId())) {
      throw new BusinessException("算法库分类ID为空");
    }

    Project project = projectMapper.selectByPrimaryKey(algoNodes.getProjectId());
    String nodeId = ComUtil.getId();
    algoNodes.setId(nodeId);
    algoNodes.setSecretLevel(project.getSecretLevel());

    insertSelective(algoNodes);

    // 插入关系表
    ProjectAlgoNodes projectAlgoNodes = new ProjectAlgoNodes();
    projectAlgoNodes.setId(ComUtil.getId());
    projectAlgoNodes.setNodesId(nodeId);
    projectAlgoNodes.setTypeId(algoNodes.getTypeId());
    projectAlgoNodes.setProjectId(algoNodes.getProjectId());
    projectAlgoNodesService.insertSelective(projectAlgoNodes);
    // 记录日志
    BdpLogUtil.log4Insert(algoNodes);

    return nodeId;
  }

  /**
   * 修改算法库
   *
   * @param algoNodes algoNodes
   */
  @Transactional(rollbackFor = Exception.class)
  public void updateNode(AlgoNodes algoNodes) {
    try {
      // 此处不能使用updateByPrimaryKey更新整个字段，否则resourceId为null，导致更新后无法查询数据。已修复该BUG。
      updateByPrimaryKeySelective(getUpdateDto(algoNodes));
    } catch (Exception e) {
      throw new BusinessException(e.getMessage(), e);
    }
  }

  private AlgoNodes getUpdateDto(AlgoNodes node) {
    AlgoNodes oldDTO = selectByPrimaryKey(node.getId());
    if (oldDTO == null) {
      throw new BusinessException("数据不存在");
    }

    // 记录日志
    BdpLogUtil.log4Update(node, oldDTO);

    //        oldDTO.setName(node.getName());
    //        oldDTO.setProgramType(node.getProgramType());
    //        oldDTO.setClassName(node.getClassName());
    //        oldDTO.setOpen(node.getOpen());
    //        oldDTO.setRemark(node.getRemark());
    //        oldDTO.setContent(node.getContent());
    //        if(StringUtils.isNotBlank(node.getParams())){
    //            oldDTO.setParams(node.getParams());
    //        }

    return node;
  }

  /**
   * 删除算法库
   *
   * @param id id
   */
  @Transactional(rollbackFor = Exception.class)
  public void deleteNode(String id) {
    if (StringUtils.isBlank(id)) {
      throw new BusinessException("删除失败！传入的参数主键为null");
    }
    try {
      // 记录日志
      AlgoNodes node = selectByPrimaryKey(id);
      if (node == null) {
        throw new BusinessException("删除失败！对象不存在");
      }

      /*AlgoNodes deleteQuery = new AlgoNodes();
      deleteQuery.setSourceId(id);
      List<AlgoNodes> deleteNode = selectList(deleteQuery);
      if (deleteNode != null && deleteNode.size() > 0) {
          throw new BusinessException("该算法库被引用，不可删除");
      }*/
      boolean deleteState = this.getDeleteState(node);

      BdpLogUtil.log4Delete(node);

      // 判断是否为最后一条引用资源的记录  是-需要删除resource  否-不可删除
      /*if (deleteState) {
        Resource resource = resourceMapper.selectByPrimaryKey(node.getResourceId());

        IFileAdapter fileAdapter =
            FileAdapterUtils.getFileAdapterByProject(resource.getProjectId());
        // 删除HDFS文件或目录
        // 判断hdfs是否存在该文件
        if (fileAdapter.exists(resource.getFullName())) {
          boolean status = fileAdapter.delete(resource.getFullName(), false);
          if (!status) {
            throw new BusinessException("hdfs删除文件失败");
          }
        }
        resourceMapper.deleteByPrimaryKey(node.getResourceId());
      }*/
      // 删除关联表
      ProjectAlgoNodes projectAlgoNodes = new ProjectAlgoNodes();
      projectAlgoNodes.setNodesId(id);
      projectAlgoNodesService.delete(projectAlgoNodes);
      deleteByPrimaryKey(id);
    } catch (Exception e) {
      throw new BusinessException(e.getMessage(), e);
    }
  }

  private boolean getDeleteState(AlgoNodes node) {
    boolean flag = false;
    String resourceId = node.getResourceId();
    AlgoNodes dto = new AlgoNodes();
    dto.setResourceId(resourceId);
    List<AlgoNodes> list = selectList(dto);
    if (list == null || list.size() < 2) {
      flag = true;
    }

    return flag;
  }

  /**
   * 判断算法库名称是否重复
   *
   * @param id id
   * @param name name
   * @return 名称是否重复
   */
  public Boolean isSameName(String id, String name, String projectId) {
    if (StringUtils.isBlank(name)) {
      throw new BusinessException("参数name为空");
    }
    if (StringUtils.isBlank(projectId)) {
      throw new BusinessException("参数projectId为空");
    }
    boolean flag = false;
    List<AlgoNodes> list = this.mapper.selectByNameAndProjectId(name, projectId);
    if (list != null && list.size() > 0) {
      if (StringUtils.isBlank(id)) {
        return true;
      }
      for (AlgoNodes dto : list) {
        if (!dto.getId().equals(id)) {
          flag = true;
          break;
        }
      }
    }
    return flag;
  }

  /**
   * 判断资源名称是否重复
   *
   * @param projectId projectId
   * @param fileName fileName
   * @return 资源名称是否重复
   */
  private boolean isSameResourceName(String projectId, String fileName) {
    boolean flag = false;
    Resource resource = new Resource();
    resource.setName(fileName);
    resource.setType(Resource.DTYPE_NODE);
    resource.setProjectId(projectId);
    List<Resource> list = resourceMapper.select(resource);
    if (list != null && list.size() > 0) {
      flag = true;
    }
    return flag;
  }

  /**
   * 算法库共享
   *
   * @param id id
   */
  @Transactional(rollbackFor = Exception.class)
  public void updateOpen(String id, Integer open) {
    AlgoNodes algoNodes = new AlgoNodes();
    algoNodes.setId(id);
    algoNodes.setOpen(open);
    updateByPrimaryKeySelective(algoNodes);
  }

  /**
   * 查询右侧数据
   *
   * @param typeId typeId
   * @param projectId projectId
   * @param keyWords keyWords
   * @param pageNo pageNo
   * @param pageSize pageSize
   * @return 右侧数据
   */
  public QueryRespBean<AlgoNodes> getList(
      String typeId, String projectId, String keyWords, Integer pageNo, Integer pageSize) {
    if (StringUtils.isBlank(typeId)) {
      throw new BusinessException("参数typeId为空");
    }
    if (StringUtils.isBlank(projectId)) {
      throw new BusinessException("参数projectId为空");
    }
    QueryRespBean<AlgoNodes> queryRespBean = new QueryRespBean<>();
    try {
      // 查询project
      Project project = projectMapper.selectByPrimaryKey(projectId);
      StorageResourceConf storageResourceConf =
          FileAdapterUtils.getStorageResourceByProjectId(projectId);
      if (storageResourceConf == null) {
        logger.error("存储资源未设置，请先设置存储资源！");
        throw new BusinessException("存储资源未设置，请先设置存储资源！");
      }
      List<String> typeIds = commonTypeService.getChildrenIds("nodes", typeId, projectId);
      if (typeIds == null) {
        typeIds = Lists.newArrayList();
      }
      typeIds.add(typeId);
      com.github.pagehelper.PageHelper.startPage(pageNo, pageSize);
      Page<AlgoNodes> dataList =
          this.mapper.getList(
              typeIds, storageResourceConf.getId(), projectId, keyWords, project.getProjectType());
      List<AlgoNodes> nodesList = dataList.getResult();
      if (nodesList != null && nodesList.size() > 0) {
        for (AlgoNodes nodes : nodesList) {
          String nodeFileName = nodes.getFileName();
          if (StringUtils.isNotBlank(nodeFileName)) {
            String fileName =
                nodeFileName.substring(nodeFileName.lastIndexOf(Constants.SINGLE_SLASH) + 1);
            nodes.setFileName(fileName);
          }
        }
      }
      queryRespBean.setResult(dataList);
      BdpLogUtil.log4Query(queryRespBean);
      return queryRespBean;
    } catch (Exception e) {
      throw new BusinessException(e.getMessage(), e);
    }
  }

  /**
   * 导入查询列表
   *
   * @param projectId projectId
   * @param keyWords keyWords
   * @param pageNo pageNo
   * @param pageSize pageSize
   */
  public QueryRespBean<AlgoNodes> getListExport(
      String projectId, String keyWords, Integer pageNo, Integer pageSize) {
    if (StringUtils.isBlank(projectId)) {
      throw new BusinessException("参数projectId为空");
    }
    QueryRespBean<AlgoNodes> queryRespBean = new QueryRespBean<>();
    try {
      com.github.pagehelper.PageHelper.startPage(pageNo, pageSize);
      Page<AlgoNodes> dataList = this.mapper.getListExport(projectId, keyWords);
      queryRespBean.setResult(dataList);
      BdpLogUtil.log4Query(queryRespBean);
      return queryRespBean;
    } catch (Exception e) {
      throw new BusinessException(e.getMessage(), e);
    }
  }

  /**
   * 导入
   *
   * @param ids ids
   */
  public void importNode(String ids, String typeId, String projectId) {
    if (StringUtils.isBlank(ids)) {
      throw new BusinessException("ids 参数为空");
    }
    if (StringUtils.isBlank(typeId)) {
      throw new BusinessException("typeId 参数为空");
    }
    if (StringUtils.isBlank(projectId)) {
      throw new BusinessException("projectId 参数为空");
    }
    String[] idArray = ids.split(",");
    for (String id : idArray) {
      AlgoNodes node = selectByPrimaryKey(id);
      AlgoNodes newNode = new AlgoNodes();
      BeanUtils.copyProperties(node, newNode);
      String nodeId = ComUtil.getId();
      newNode.setId(nodeId);
      newNode.setSourceId(node.getId());
      newNode.setOpen(AlgoNodes.OPEN_ADDUCTION);
      insertSelective(newNode);
      ProjectAlgoNodes projectAlgoNodes = new ProjectAlgoNodes();
      projectAlgoNodes.setNodesId(nodeId);
      projectAlgoNodes.setProjectId(projectId);
      projectAlgoNodes.setTypeId(typeId);
      projectAlgoNodes.setId(ComUtil.getId());
      projectAlgoNodesService.insertSelective(projectAlgoNodes);

      // 记录日志
      String logTitle = "算法导入：【" + newNode.getName() + "】";
      BdpLogUtil.log("算法库管理模块", logTitle, PlatformConstant.OpType.update);
    }
  }

  public void download(String nodeId, HttpServletResponse response) {
    AlgoNodes algoNodes = this.selectByPrimaryKey(nodeId);
    if (algoNodes == null) {
      throw new BusinessException("算法库不存在");
    }
    String content = algoNodes.getContent();
    try {
      // 设置文件ContentType类型，这样设置，会自动判断下载文件类型
      response.setContentType("multipart/form-data");
      // 设置文件头：最后一个参数是设置下载文件名
      String fileName = algoNodes.getName() + ".py";
      logger.info("下载的文件名称为：{}", fileName);
      fileName = URLEncoder.encode(fileName, "utf-8");
      // 处理空格转为加号的问题
      String dealedFileName = fileName.replaceAll("\\+", "%20");
      logger.info("转码后的文件名为：{}", dealedFileName);
      response.setHeader("Content-Disposition", "attachment;filename=" + dealedFileName);
      InputStream inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
      IOUtils.copy(inputStream, response.getOutputStream());
      IOUtils.closeQuietly(inputStream);
    } catch (IOException ioe) {
      throw new BusinessException("下载文件失败!");
    }
  }

  /**
   * 查看算法节点详情
   *
   * @param id
   * @return
   */
  public AlgoNodes getAlgoNodeInfo(String id) {
    // step1:获取算法节点信息
    AlgoNodes algoNodes = queryAlgoNodeById(id);
    if (algoNodes == null) {
      throw new BusinessException("算法节点不存在，id=" + id);
    }

    // step2:获取算法节点对应typeId
    String typeId = this.mapper.getTypeIdByAlgoNodeId(id);
    algoNodes.setTypeId(typeId);

    return algoNodes;
  }

  /**
   * 根据id查询算法信息
   *
   * @param id
   * @return
   */
  private AlgoNodes queryAlgoNodeById(String id) {
    if (StringUtils.isEmpty(id)) {
      return null;
    }

    return this.selectByPrimaryKey(id);
  }

  public void deleteByProjectId(String projectId) {
    this.mapper.deleteByProjectId(projectId);
  }
}
